const test = require('test');
test.setup();

const http = require('http');

const { check_result } = require('../../test/_utils');
const { useTestServer } = require('../../test/support/spec_helper');

let counter = 1000;
function getCounterId () {
    return counter++
}

describe("extend multiple level", () => {
    var l1_id;
    var l1a_id;
    var TESTDATA = require('./__test__/mock-data.json');

    const { tSrvInfo, clientCtx, clients } = useTestServer({})

    before(() => {
        var rep = http.post(tSrvInfo.appUrlBase + '/level', {
            json: TESTDATA
        });
        
        assert.equal(rep.statusCode, 201)
        l1_id = rep.json()[0].id
        l1a_id = rep.json()[1].id
    });

    describe("forbid rest operation to non-single-key table: when `rest.model.disable_access_composite_table`", () => {
        it('cannot get table generated by .extendsTo()', () => {
            var rep = http.get(tSrvInfo.appUrlBase + `/level_lproperty/${getCounterId()}`, {
            });

            assert.equal(rep.statusCode, 404)
            assert.equal(rep.json().message, "Missing or invalid classname 'level_lproperty'.")
        });

        it('cannot post table generated by .extendsTo()', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/level_lproperty`, {
                json: {}
            });

            assert.equal(rep.statusCode, 404)
            assert.equal(rep.json().message, "Missing or invalid classname 'level_lproperty'.")
        });

        it('cannot put table generated by .extendsTo()', () => {
            var rep = http.put(tSrvInfo.appUrlBase + `/level_lproperty/${getCounterId()}`, {
                json: {}
            });

            assert.equal(rep.statusCode, 404)
            assert.equal(rep.json().message, "Missing or invalid classname 'level_lproperty'.")
        });

        it('cannot del table generated by .extendsTo()', () => {
            var rep = http.del(tSrvInfo.appUrlBase + `/level_lproperty/${getCounterId()}`, {
                json: {}
            });

            assert.equal(rep.statusCode, 404)
            assert.equal(rep.json().message, "Missing or invalid classname 'level_lproperty'.")
        });
    })

    it('find level', () => {
        ;[
            'l1',
            'l1-l2',
            'l1-l2-l3',

            'l1a',
        ].forEach(key => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: {
                            name: "${key}:name"
                        }
                    ){
                        id,
                        name
                    }
                }`
            });
            
            assert.equal(rep.statusCode, 200);
            switch (key) {
                case 'l1':
                    check_result(rep.json().data.find_level[0], {
                        "id": l1_id,
                        "name": `${key}:name`
                    })
                    break
                case 'l1a':
                    check_result(rep.json().data.find_level[0], {
                        "id": l1a_id,
                        "name": `${key}:name`
                    })
                    break
                default:
                    check_result(rep.json().data.find_level[0], {
                        "name": `${key}:name`
                    }, [
                        'createdAt',
                        'updatedAt',
                        'id'
                    ]);
                    break
            }
        })
    });

    it('findby with non-existing extend', () => {
        var rep = http.post(tSrvInfo.appUrlBase + `/`, {
            headers: {
                'Content-Type': 'application/graphql'
            },
            body: `{
                find_level(
                    findby: {
                        extend: "non_existed_extend",
                        where: {
                            name: "l1-l2:name"
                        }
                    }
                ){
                    id,
                    name
                }
            }`
        });

        assert.equal(rep.statusCode, 200);

        assert.equal(
            rep.json().errors[0].message,
            "invalid association symbol 'non_existed_extend' for model 'level'"
        )
    });

    describe('find where/whereExists, extra where sublevel with has-many assoc', () => {
        const t = Date.now();
        const staticId = getCounterId();
        const l1_many_sublevels = TESTDATA[0].many_sublevels;
        const l1a_many_sublevels = TESTDATA[1].many_sublevels;

        it('only findby in association, use `ne`', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        findby: {
                            extend: "many_sublevels"
                            on: { many_sublevels_id: { ne: ${staticId} } }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                l1_many_sublevels
            );
        })
        
        it('only findby in association, use `eq`', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        findby: {
                            extend: "many_sublevels"
                            on: { many_sublevels_id: "${staticId}" }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                undefined,
            )
        })
        
        it('only findby in association, invalid Date arg', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        findby: {
                            extend: "many_sublevels"
                            on: { since: { eq: "${t}" } }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                undefined,
            )
        })

        it('only findby in association, valid Date arg', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        findby: {
                            extend: "many_sublevels"
                            on: {
                                since: {
                                    lte: "${l1_many_sublevels[0].extra.since}"
                                    modifiers: {
                                        is_date: true
                                    }
                                }
                            }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                l1_many_sublevels
            );
        })

        it('l1 where, findby in association - 1', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: { id: { eq: ${staticId} } }
                        findby: {
                            extend: "many_sublevels"
                            on: { many_sublevels_id: { ne: ${staticId} } }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                undefined
            )
        });

        it('l1 where, findby in association - 2', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: { level_f: { eq: "non-exist-level_f-value" } }
                        findby: {
                            extend: "many_sublevels"
                            on: { many_sublevels_id: { ne: ${staticId} } }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                undefined
            )
        });

        it('l1 where, findby in association - 3', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: { level_f: "l1:level_f" }
                        findby: {
                            extend: "many_sublevels"
                            on: { many_sublevels_id: { ne: ${staticId} } }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                l1_many_sublevels
            );
        });

        it('l1 where, findby in association - 4', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: { level_f: "l1:level_f" }
                        findby: {
                            extend: "one_l2"
                            where: { name: { eq: "l1-l2:name" } }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                l1_many_sublevels
            );
        });

        it('l1 where, findby in association - 5', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: { level_f: "l1:level_f" }
                        findby: {
                            extend: "one_l2"
                            where: { name: { eq: "non-exist--one_l2-value" } }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);

            check_result(
                rep.json().data.find_level[0],
                undefined
            );
        });

        it('l1 where, findby in l1 association, l2 where', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                // TODO: improve query stages here, use one query rather than two queries
                body: `{
                    find_level(
                        where: { level_f: "l1:level_f" }
                        findby: {
                            extend: "one_l2"
                            where: { name: { eq: "l1-l2:name" } }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                            where: { name: { ne: "${l1_many_sublevels[1].name}" } }
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                l1_many_sublevels.filter( x => x !== l1_many_sublevels[1])
            );
        });

        it('l1 where with multiple conditions, findby in association', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: { id: { ne: ${staticId} }, name: "l1:name" }
                        findby: {
                            extend: "many_sublevels"
                            on: { many_sublevels_id: { ne: ${staticId} } }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                l1_many_sublevels
            );
        });

        it('l1 where with multiple conditions using `like`, findby in association', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: { name: { like: "%:name%" } }
                        findby: {
                            extend: "many_sublevels"
                            on: { many_sublevels_id: { ne: ${staticId} } }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                l1_many_sublevels
            );
        });

        it('findby in l1 association, where for l2 extra fields', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        findby: {
                            extend: "many_sublevels"
                            on: {
                                since: {
                                    eq: "${l1_many_sublevels[0].extra.since}"
                                    modifiers: {
                                        is_date: true
                                    }
                                }
                            }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                            where: {
                                since: {
                                    eq: "${l1_many_sublevels[0].extra.since}"
                                    modifiers: {
                                        is_date: true
                                    }
                                }
                            }
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                [l1_many_sublevels[0]]
            );
        });

        it('get l2 by l1 --- [1]', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: {
                            name: "l1:name"
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                l1_many_sublevels
            );
        });

        it('get l2 by l1 --- [2]', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: {
                            name: "l1a:name"
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1a:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                l1a_many_sublevels
            );
        });

        it('get l2 by l1, findby l3 in l2 --- [1]', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: {
                            name: "l1a:name"
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                            findby: {
                                extend: "subl_one_descendant_level"
                                where: {
                                    name: "${l1_many_sublevels[2].subl_one_descendant_level.name}"
                                }
                            }
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1a:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                []
            );
        });

        it('get l2 by l1, findby l3 in l2 --- [2]', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: {
                            name: "l1a:name"
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                            findby: {
                                extend: "subl_one_descendant_level"
                                where: {
                                    name: "${l1a_many_sublevels[2].subl_one_descendant_level.name}"
                                }
                            }
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1a:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                [ l1a_many_sublevels[2] ]
            );
        });

        it('get l2 by l1, findby l3 in l2, extra where in l2 --- [3]', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: {
                            name: "l1a:name"
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                            where: {
                                name: "${l1a_many_sublevels[2].name}"
                            }
                            findby: {
                                extend: "subl_one_descendant_level"
                                where: {
                                    name: "${l1a_many_sublevels[2].subl_one_descendant_level.name}"
                                }
                            }
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1a:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                [ l1a_many_sublevels[2] ]
            );
        });

        it('get l2 by l1, findby l3 in l2, extra where in l2 --- [4]', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: {
                            name: "l1a:name"
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                            findby: {
                                extend: "subl_one_descendant_level"
                                where: {
                                    name: "${l1a_many_sublevels[2].subl_one_descendant_level.name}"
                                }
                            }
                            where: {
                                since: {
                                    ne: "${l1a_many_sublevels[2].extra.since}"
                                    modifiers: {
                                        is_date: true
                                    }
                                }
                            }
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1a:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                []
            );
        });

        it('get l2 by l1, findby l3 in l2, extra where in l2 ---  [5]', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: {
                            name: "l1a:name"
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                            findby: {
                                extend: "subl_one_descendant_level"
                                where: {
                                    name: "${l1a_many_sublevels[2].subl_one_descendant_level.name}"
                                }
                            }
                            where: {
                                since: {
                                    eq: "${l1a_many_sublevels[2].extra.since}"
                                    modifiers: {
                                        is_date: true
                                    }
                                }
                            }
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1a:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                [ l1a_many_sublevels[2] ]
            );
        });

        it('get l2 by l1, findby l3 in l2, where & extra_where in l2 --- [6]', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: {
                            name: "l1a:name"
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                            where: {
                                name: {
                                    like: "%${l1a_many_sublevels[1].name}%"
                                }
                                since: {
                                    eq: "${l1a_many_sublevels[1].extra.since}"
                                    modifiers: {
                                        is_date: true
                                    }
                                }
                            }
                            findby: {
                                extend: "subl_one_descendant_level"
                                where: {
                                    name: "${l1a_many_sublevels[1].subl_one_descendant_level.name}"
                                }
                            }
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1a:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                [ l1a_many_sublevels[1] ]
            );
        });

        it('only extra_where for extra fields', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        order: "-name"
                        where: {
                            name: {
                                ne: "l1a:name"
                            } 
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                            where: {
                                since: {
                                    ne: "${l1_many_sublevels[1].extra.since}"
                                    modifiers: {
                                        is_date: true
                                    }
                                }
                            }
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            check_result(
                rep.json().data.find_level[0],
                { name: `l1:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                [l1_many_sublevels[0], l1_many_sublevels[2]]
            );
        });
    });
    
    describe('[join_where/extra_where] findby in l1 association, join_where for l2 extra fields', () => {
        ['join_where', 'extra_where'].forEach((compat_op) => {
            
        it(`compat_op: ${compat_op}`, () => {
            const l1_many_sublevels = TESTDATA[0].many_sublevels;

            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        findby: {
                            extend: "many_sublevels"
                            on: {
                                since: {
                                    eq: "${l1_many_sublevels[0].extra.since}"
                                    modifiers: {
                                        is_date: true
                                    }
                                }
                            }
                        }
                    ){
                        id,
                        name,
                        many_sublevels(
                            order: "name"
                            ${compat_op}: {
                                since: {
                                    eq: "${l1_many_sublevels[0].extra.since}"
                                    modifiers: {
                                        is_date: true
                                    }
                                }
                            }
                        ){
                            id,
                            name,
                            extra{
                                since
                            }
                            subl_one_descendant_level{
                                name
                                descendant_level_f
                            }
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            
            // assert.equal(
            //     rep.json().data.find_level[0],
            //     undefined
            // )
            check_result(
                rep.json().data.find_level[0],
                { name: `l1:name` },
                [ 'createdAt', 'updatedAt', 'id', 'many_sublevels' ]
            )
            
            check_result(
                rep.json().data.find_level[0].many_sublevels.map(x => ({
                    name: x.name,
                    extra: x.extra,
                    subl_one_descendant_level: x.subl_one_descendant_level
                })),
                // extra_results is order by 'name'
                [l1_many_sublevels[0]]
            );
        })
        
        })
    })

    function assert_found_level_with_one_l2 (rep, fetcher) {
        check_result(
            fetcher(rep)[0],
            {
                name: `l1:name`,
            },
            [
                'createdAt',
                'updatedAt',
                'id',
                'one_l2'
            ]
        );
        assert.equal(fetcher(rep).length, 1);

        check_result(
            fetcher(rep)[0].one_l2,
            {
                "name": "l1-l2:name"
            }
        );
    }
    
    describe('find by level with has-one self assoc', () => {
        it('only findby', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        findby: {
                            extend: "one_l2",
                            where: {
                                name: "l1-l2:name"
                            }
                        }
                    ){
                        id,
                        name,
                        one_l2{
                            name
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            assert_found_level_with_one_l2(rep, rep => rep.json().data.find_level);
        });
        
        it('l1 where, findby', () => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: { name: "l1a:name" }
                        findby: {
                            extend: "one_l2",
                            where: {
                                name: "l1-l2:name"
                            }
                        }
                    ){
                        id,
                        name,
                        one_l2{
                            name
                        }
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            assert.deepEqual(rep.json().data.find_level, []);
        });
    });

    it('paging find by level with has-one self assoc', () => {
        var rep = http.post(tSrvInfo.appUrlBase + `/`, {
            headers: {
                'Content-Type': 'application/graphql'
            },
            body: `{
                paging_level(
                    findby: {
                        extend: "one_l2",
                        where: {
                            name: "l1-l2:name"
                        }
                    }
                ){
                    results{
                        id,
                        name,
                        one_l2{
                            name
                        }
                    }
                    count
                }
            }`
        });

        assert.equal(rep.statusCode, 200);

        assert_found_level_with_one_l2(rep, rep => rep.json().data.paging_level.results);
        assert.equal(rep.json().data.paging_level.count, 1);
    });

    function assert_found_level_with_sublevel (rep, fetcher) {
        check_result(
            fetcher(rep)[0],
            {
                name: `l1:name`,
            },
            [
                'createdAt',
                'updatedAt',
                'id',
                'one_sl'
            ]
        );
        assert.equal(fetcher(rep).length, 1);

        check_result(
            fetcher(rep)[0].one_sl,
            {
                "name": "l1-sl:name"
            }
        );
    }

    it('find by sublevel with has-one assoc', () => {
        var rep = http.post(tSrvInfo.appUrlBase + `/`, {
            headers: {
                'Content-Type': 'application/graphql'
            },
            body: `{
                find_level(
                    findby: {
                        extend: "one_sl",
                        where: {
                            name: "l1-sl:name"
                        }
                    }
                ){
                    id,
                    name,
                    one_sl{
                        name
                    }
                }
            }`
        });

        assert.equal(rep.statusCode, 200);
        assert_found_level_with_sublevel(rep, rep => rep.json().data.find_level);
    });

    it('paging find by sublevel with has-one assoc', () => {
        var rep = http.post(tSrvInfo.appUrlBase + `/`, {
            headers: {
                'Content-Type': 'application/graphql'
            },
            body: `{
                paging_level(
                    findby: {
                        extend: "one_sl",
                        where: {
                            name: "l1-sl:name"
                        }
                    }
                ){
                    results{
                        id,
                        name,
                        one_sl{
                            name
                        }
                    }
                    count
                }
            }`
        });

        assert.equal(rep.statusCode, 200);
        assert_found_level_with_sublevel(rep, rep => rep.json().data.paging_level.results);
        assert.equal(rep.json().data.paging_level.count, 1);
    });

    it('find sublevel', () => {
        ;[
            'l1-sl',
            'l1-sl-subl',
        ].forEach(key => {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_sub_level(
                        where: {
                            name: "${key}:name"
                        }
                    ){
                        id,
                        name
                    }
                }`
            });

            assert.equal(rep.statusCode, 200);
            check_result(rep.json().data.find_sub_level[0], 
                {
                    "name": `${key}:name`
                },
                [
                    'createdAt',
                    'updatedAt',
                    'id'
                ]
            );
        });
    });

    describe('extendsTo association', () => {
        let add_count = 0;
        function count_increament () {
            return TESTDATA[0].lproperty.weight + (++add_count)
        }
        function cur_weight () {
            return TESTDATA[0].lproperty.weight + add_count
        }

        function get_uniq_extendsTo_property () {
            var rep = http.get(tSrvInfo.appUrlBase + `/level/${l1_id}/lproperty`, {
                query: {}
            });

            assert.equal(rep.statusCode, 200);
            return rep.json()
        }

        function get_uniq_extendsTo_property_by_gql () {
            var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                headers: {
                    'Content-Type': 'application/graphql'
                },
                body: `{
                    find_level(
                        where: {
                            name: "l1:name"
                        }
                    ){
                        id,
                        name,
                        lproperty{
                            name
                            weight
                        }
                    }
                }`
            });

            return rep.json()
        }

        function assert_uniq_extendsTo_property (l1_lproperty) {
            assert.equal(l1_lproperty.name, "l1:level_property-name")
            assert.equal(l1_lproperty.weight, 987654321 + add_count)
        }

        it(`created level_lproperty`, () => {
            var l1_lproperty = get_uniq_extendsTo_property()
            assert_uniq_extendsTo_property(l1_lproperty)

            var l1_lproperty = get_uniq_extendsTo_property_by_gql().data.find_level[0].lproperty
            assert_uniq_extendsTo_property(l1_lproperty)
        })

        it(`find/get, update level's level_lproperty`, () => {
            ;[
                null
            ].forEach(key => {
                var rep = http.put(tSrvInfo.appUrlBase + `/level/${l1_id}/lproperty`, {
                    query: {},
                    json: {
                        weight: count_increament()
                    }
                });

                assert.equal(rep.statusCode, 200);
                assert.property(rep.json(), 'id');
                assert.property(rep.json(), 'updatedAt');
            });
        })

        it(`return null after level's level_lproperty removed, then recreate it`, () => {
            var l1_lproperty = get_uniq_extendsTo_property();
            assert_uniq_extendsTo_property(l1_lproperty);

            remove_it: {
                var rep = http.del(tSrvInfo.appUrlBase + `/level/${l1_id}/lproperty/${l1_lproperty.id}`, {
                    query: {},
                    body: {}
                });

                assert.equal(rep.statusCode, 200);
                assert.property(rep.json(), 'id');
                assert.property(rep.json(), 'updatedAt');

                var l1_lproperty = get_uniq_extendsTo_property();
                assert.equal(l1_lproperty, null)


                var l1_lproperty = get_uniq_extendsTo_property_by_gql().data.find_level[0].lproperty
                assert.equal(l1_lproperty, null)
            }

            recreate: {
                var rep = http.post(tSrvInfo.appUrlBase + `/level/${l1_id}/lproperty`, {
                    query: {},
                    json: {
                        ...TESTDATA[0].lproperty,
                        weight: count_increament(),
                    }
                });

                assert.equal(rep.statusCode, 201);
                assert.property(rep.json(), 'id');
                assert.property(rep.json(), 'createdAt');

                var l1_lproperty = get_uniq_extendsTo_property();
                assert_uniq_extendsTo_property(l1_lproperty);

                var l1_lproperty = get_uniq_extendsTo_property_by_gql().data.find_level[0].lproperty
                assert_uniq_extendsTo_property(l1_lproperty);
            }
        });

        describe('base findby extend field', () => {
            it('find by query: where lproperty.weight=...', () => {
                var rep = http.get(tSrvInfo.appUrlBase + `/level`, {
                    query: {
                        findby: JSON.stringify(
                            {
                                extend: "lproperty",
                                where: {
                                    weight: {
                                        eq: cur_weight()
                                    }
                                }
                            }
                        )
                    }
                });

                assert.equal(rep.statusCode, 200);
                assert.equal(rep.json().length, 1);
            });

            /**
             * there are more than 1 'level' items, but ONLY 1 has its `lproperty`,
             * when I `findby` by lproperty with SQL, the pre-requisite contains that
             * `level must has its own lproperty`. So there is 0
             */
            it('find by query: where lproperty.weight != ...', () => {
                var rep = http.get(tSrvInfo.appUrlBase + `/level`, {
                    query: {
                        findby: JSON.stringify(
                            {
                                extend: "lproperty",
                                where: {
                                    weight: {
                                        ne: cur_weight()
                                    }
                                }
                            }
                        )
                    }
                });

                assert.equal(rep.statusCode, 200);
                assert.equal(rep.json().length, 0);
            });

            it('find by graphql: where lproperty.weight =. ..', () => {
                var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                    headers: {
                        'Content-Type': 'application/graphql'
                    },
                    body: `{
                        find_level(
                            findby: {
                                extend: "lproperty",
                                where: {
                                    weight: {
                                        eq: ${cur_weight()}
                                    }
                                }
                            }
                        ){
                            id
                            lproperty{
                                name
                                weight
                            }
                        }
                    }`
                });

                assert.equal(rep.statusCode, 200);
                assert.equal(rep.json().data.find_level.length, 1);
            });

            it('find by graphql: where lproperty.weight != ...', () => {
                var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                    headers: {
                        'Content-Type': 'application/graphql'
                    },
                    body: `{
                        find_level(
                            findby: {
                                extend: "lproperty",
                                where: {
                                    weight: {
                                        ne: ${cur_weight()}
                                    }
                                }
                            }
                        ){
                            id
                            lproperty{
                                name
                                weight
                            }
                        }
                    }`
                });

                assert.equal(rep.statusCode, 200);
                assert.equal(rep.json().data.find_level.length, 0);
            });

            it('paging by graphql: where lproperty.weight =. .. ', () => {
                var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                    headers: {
                        'Content-Type': 'application/graphql'
                    },
                    body: `{
                        paging_level(
                            findby: {
                                extend: "lproperty",
                                where: {
                                    weight: {
                                        eq: ${cur_weight()}
                                    }
                                }
                            }
                        ){
                            results {
                                id
                                lproperty{
                                    name
                                    weight
                                }
                            }
                            count
                        }
                    }`
                });

                assert.equal(rep.statusCode, 200);
                assert.equal(rep.json().data.paging_level.count, 1);
                assert.equal(rep.json().data.paging_level.results.length, 1);
            });

            it('paging by graphql: where lproperty.weight != ... ', () => {
                var rep = http.post(tSrvInfo.appUrlBase + `/`, {
                    headers: {
                        'Content-Type': 'application/graphql'
                    },
                    body: `{
                        paging_level(
                            findby: {
                                extend: "lproperty",
                                where: {
                                    weight: {
                                        ne: ${cur_weight()}
                                    }
                                }
                            }
                        ){
                            results {
                                id
                                lproperty{
                                    name
                                    weight
                                }
                            }
                            count
                        }
                    }`
                });

                assert.equal(rep.statusCode, 200);
                assert.equal(rep.json().data.paging_level.count, 0);
                assert.equal(rep.json().data.paging_level.results.length, 0);
            });
        });
    })
});

if (require.main === module) {
    test.run(console.DEBUG);
    process.exit();
}

function gettime(m) {
    return (new Date(m)).getTime()
}